//bange_behavior.cpp
//Copyright (C) 2010-2011 Oscar (.teri) Triano

//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <SFML/Graphics.hpp>
#include <bange_base.hpp>
#include <bange_behavior.hpp>
#include <typeinfo>

#define BEHAVIOR_DEBUG

bange::behavior::behavior(lua_State *vm){
	lua_createtable(vm, 0, 16);//16 event handlers
	this->events = luaL_ref(vm, LUA_REGISTRYINDEX);
	this->eventhandler = false;
	this->processfunctions = true;
	this->processschedulefunctions = true;
	this->data = LUA_REFNIL;
}

bool bange::behavior::NewIndex(lua_State *vm){
	//userdata, key, data
	char key[64] = {0};
	strncpy(key, lua_tostring(vm, 2), 64);
	if (strcmp(("data"), key) == 0){
		luaL_unref(vm, LUA_REGISTRYINDEX, this->data);
		this->data = luaL_ref(vm, LUA_REGISTRYINDEX);
		return true;}
	if (strcmp("eventhandler", key) == 0 && lua_isboolean(vm, 3)){
		this->eventhandler = static_cast<bool>(lua_toboolean(vm, 3));
		return true;}
	if (strcmp("processfunctions", key) == 0 && lua_isboolean(vm, 3)){
		this->processfunctions = static_cast<bool>(lua_toboolean(vm, 3));
		return true;}
	if (strcmp("processschedulefunctions", key) == 0 && lua_isboolean(vm, 3)){
		this->processschedulefunctions = static_cast<bool>(lua_toboolean(vm, 3));
		return true;}
	if (!lua_isfunction(vm, 3)){
		return false;}
	//Watch out, NewIndex will store a function as event handler
	lua_rawgeti(vm, LUA_REGISTRYINDEX, this->events);
	lua_pushvalue(vm, 3);
	lua_setfield(vm, -2, key);
	return true;
}

bool bange::behavior::Index(lua_State *vm){
	//userdata, key
	char key[64] = {0};
	strncpy(key, lua_tostring(vm, 2), 64);
	if(strcmp("data", key) == 0){
		lua_rawgeti(vm, LUA_REGISTRYINDEX, this->data);
		return true;}
	if(strcmp("eventhandler", key) == 0){
		lua_pushboolean(vm, static_cast<int>(this->eventhandler));
		return true;}
	if(strcmp("processfunctions", key) == 0){
		lua_pushboolean(vm, static_cast<int>(this->processfunctions));
		return true;}
	if(strcmp("processschedulefunctions", key) == 0){
		lua_pushboolean(vm, static_cast<int>(this->processschedulefunctions));
		return true;}
	//Watch out, Index will return a method
	lua_getfield(vm, LUA_REGISTRYINDEX, "bange::behavior_method");
	lua_getfield(vm, -1, key);
	if(lua_isfunction(vm, -1)){
		return true;}
	return false;
}

void bange::behavior::SetEventHandler(bool eventhandler){
	this->eventhandler = eventhandler;
}

void bange::behavior::AddFunction(lua_State *vm){
	//userdata, function
	const void *key = lua_topointer(vm, 2);
	if (this->functions.find(key) != this->functions.end()){
		return;}
	bange::function *newfunction = new bange::function;
	newfunction->enabled = true;
	newfunction->function = luaL_ref(vm, LUA_REGISTRYINDEX);
	this->functions[key] = newfunction;
}

bool bange::behavior::SetFunction(lua_State *vm){
	//userdata, function, set
	const void *key = lua_topointer(vm, 2);
	map<const void *, bange::function *>::iterator function;
	function = this->functions.find(key);
	if (function == this->functions.end()){
		return false;}
	function->second->enabled = static_cast<bool>(lua_toboolean(vm, 3));
	return true;
}

bool bange::behavior::QuitFunction(lua_State *vm){
	//userdata, function
	const void *key = lua_topointer(vm, 2);
	map<const void *, bange::function *>::iterator function;
	function = this->functions.find(key);
	if (function == this->functions.end()){
		return false;}
	luaL_unref(vm, LUA_REGISTRYINDEX, function->second->function);
	delete function->second;
	this->functions.erase(function);
	return true;
}

void bange::behavior::AddScheduleFunction(float time, lua_State *vm){
	//userdata, function, number
	const void *key = lua_topointer(vm, 2);
	map<const void *, bange::schedulefunction *>::iterator schedulefunction;
	schedulefunction = this->schedulefunctions.find(key);
	if(schedulefunction != this->schedulefunctions.end()){
		schedulefunction->second->time = time;
		schedulefunction->second->storetime = 0.0f;
		return;
	}
	bange::schedulefunction *newschedulefunction = new bange::schedulefunction;
	lua_pushvalue(vm, 2);
	newschedulefunction->function = luaL_ref(vm, LUA_REGISTRYINDEX);
	newschedulefunction->enabled = true;
	newschedulefunction->storetime = 0.0f;
	newschedulefunction->time = time;
	this->schedulefunctions[key] = newschedulefunction;
}

bool bange::behavior::SetScheduleFunction(lua_State *vm){
	//userdata, function, set
	const void *key = lua_topointer(vm, 2);
	map<const void *, bange::schedulefunction *>::iterator schedulefunction;
	schedulefunction = this->schedulefunctions.find(key);
	if (schedulefunction == this->schedulefunctions.end()){
		return false;}
	schedulefunction->second->enabled = static_cast<bool>(lua_toboolean(vm, 3));
	return true;
}

void bange::behavior::ResetScheduleFunction(lua_State *vm){
	//userdata, function
	const void *key = lua_topointer(vm, 2);
	map<const void *, bange::schedulefunction *>::iterator schedulefunction;
	schedulefunction = this->schedulefunctions.find(key);
	if (schedulefunction == this->schedulefunctions.end()){
		return;}
	schedulefunction->second->storetime = 0.0f;
}

bool bange::behavior::QuitScheduleFunction(lua_State *vm){
	//userdata, function
	const void *key = lua_topointer(vm, 2);
	map<const void *, bange::schedulefunction *>::iterator schedulefunction;
	schedulefunction = this->schedulefunctions.find(key);
	if( schedulefunction == this->schedulefunctions.end()){
		return false;}
	luaL_unref(vm, LUA_REGISTRYINDEX, schedulefunction->second->function);
	delete schedulefunction->second;
	this->schedulefunctions.erase(schedulefunction);
	return true;
}

void bange::behavior::ExecuteEvents(int indexuserdata, int indexevent, sf::Event &event, lua_State *vm){
	if (this->eventhandler != true){
		return;}
	lua_rawgeti(vm, LUA_REGISTRYINDEX, this->events);
	using namespace sf;
	switch (event.Type){
        case Event::KeyPressed: case Event::KeyReleased:
            if (event.Type == Event::KeyPressed){
                lua_getfield(vm, -1, "KeyPressed");}
            if (event.Type == Event::KeyReleased){
                lua_getfield(vm, -1, "KeyReleased");}
        break;
        case Event::MouseButtonPressed: case Event::MouseButtonReleased:
            if (event.Type == Event::MouseButtonPressed){
                lua_getfield(vm, -1, "MouseButtonPressed");}
            if (event.Type == Event::MouseButtonReleased){
                lua_getfield(vm, -1, "MouseButtonReleased");}
        break;
        case Event::MouseMoved:
            lua_getfield(vm, -1, "MouseMoved");
        break;
        case Event::MouseWheelMoved:
            lua_getfield(vm, -1, "MouseWheelMoved");
        break;
        case Event::JoyButtonPressed: case Event::JoyButtonReleased:
            if (event.Type == Event::JoyButtonPressed){
                lua_getfield(vm, -1, "JoyButtonPressed");}
            if (event.Type == Event::JoyButtonReleased){
                lua_getfield(vm, -1, "JoyButtonReleased");}
        break;
        case Event::JoyMoved:
            lua_getfield(vm, -1, "JoyMoved");
        break;
        case Event::TextEntered:
            lua_getfield(vm, -1, "TextEntered");
        break;
        case Event::LostFocus: case Event::GainedFocus:
            if (event.Type == Event::LostFocus){
                lua_getfield(vm, -1, "LostFocus");}
            if (event.Type == Event::GainedFocus){
                lua_getfield(vm, -1, "GainedFocus");}
        break;
        default:
			lua_pop(vm, 1);//Events table
			return;
        break;//redundant
    }
    if(!lua_isfunction(vm, -1)){
		lua_pop(vm, 2);//Nil and events table;
		return;}
	lua_pushvalue(vm, indexuserdata);
	lua_pushvalue(vm, indexevent);
	if (lua_pcall(vm, 2, 0, 0) == LUA_ERRRUN){
		luaL_error(vm, "bange: Error in event handler: %s", lua_tostring(vm, -1));}
	lua_pop(vm, 1);//Events table
}

void bange::behavior::ExecuteFunctions(int indexuserdata, lua_State *vm){
	if (this->processfunctions != true){
		return;}
	map<const void *, bange::function *>::iterator function;
	for(function = this->functions.begin(); function != this->functions.end(); function++){
		if (function->second->enabled == false){
			continue;}
		lua_rawgeti(vm, LUA_REGISTRYINDEX, function->second->function);
		lua_pushvalue(vm, indexuserdata);
		if (lua_pcall(vm, 1, 0, 0) == LUA_ERRRUN){
			luaL_error(vm, "bange: Error calling function: %s", lua_tostring(vm, -1));}
	}
}

void bange::behavior::ExecuteScheduleFunctions(int indexuserdata, float frametime, lua_State *vm){
	if (this->processschedulefunctions != true){
		return;}
	map<const void *, bange::schedulefunction *>::iterator schedulefunction;
	for(schedulefunction = this->schedulefunctions.begin(); schedulefunction != this->schedulefunctions.end(); schedulefunction++){
		if (schedulefunction->second->enabled == false){
			continue;}
		schedulefunction->second->storetime += frametime;
		if (schedulefunction->second->storetime < schedulefunction->second->time){
			continue;}
		lua_rawgeti(vm, LUA_REGISTRYINDEX, schedulefunction->second->function);
		lua_pushvalue(vm, indexuserdata);
		if (lua_pcall(vm, 1, 0, 0) == LUA_ERRRUN){
			luaL_error(vm, "bange: Error calling schedule function: %s", lua_tostring(vm, -1));}
		schedulefunction->second->storetime = 0.0f;
	}
}

void bange::behavior::CleanVM(lua_State *vm){
	luaL_unref(vm, LUA_REGISTRYINDEX, this->events);
	map<const void *, bange::function *>::iterator function;
	map<const void *, bange::schedulefunction *>::iterator schedulefunction;
	for(function = this->functions.begin(); function != this->functions.end(); function++){
		luaL_unref(vm, LUA_REGISTRYINDEX, function->second->function);
	}
	for(schedulefunction = this->schedulefunctions.begin(); schedulefunction != this->schedulefunctions.end(); schedulefunction++){
		luaL_unref(vm, LUA_REGISTRYINDEX, schedulefunction->second->function);
	}
}

bange::behavior::~behavior(){
	map<const void *, bange::function *>::iterator function;
	map<const void *, bange::schedulefunction *>::iterator schedulefunction;
	for(function = this->functions.begin(); function != this->functions.end(); function++){
		delete function->second;
	}
	for(schedulefunction = this->schedulefunctions.begin(); schedulefunction != this->schedulefunctions.end(); schedulefunction++){
		delete schedulefunction->second;
	}
	this->functions.clear();
	this->schedulefunctions.clear();
	#ifdef BEHAVIOR_DEBUG
	cout << "bange::behavior destructor for: " << this << endl;
	#endif
}

void bange::behavior::RegisterVM(lua_State *vm){
	luaL_Reg method[] = {
	{"AddFunction", bangebehavior_AddFunction},
    {"SetFunction", bangebehavior_SetFunction},
    {"QuitFunction", bangebehavior_QuitFunction},
    {"AddScheduleFunction", bangebehavior_AddScheduleFunction},
    {"SetScheduleFunction", bangebehavior_SetScheduleFunction},
    {"ResetScheduleFunction", bangebehavior_ResetScheduleFunction},
    {"QuitScheduleFunction", bangebehavior_QuitScheduleFunction},
	{NULL, NULL}};
	luaL_newmetatable(vm, "bange::behavior_method");
	luaL_register(vm, NULL, method);
	lua_pop(vm, 1);
}

static int bangebehavior_AddFunction(lua_State *vm){
	//scene, function
	if(!lua_isfunction(vm, 2)){
		cout << "bange: AddFunction: First argument isn't a function" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	bange::base *base = bange::base::GetBaseFromProxy(1, vm);
	bange::behavior *behavior = dynamic_cast<bange::behavior *>(base);
	behavior->AddFunction(vm);
	lua_pushboolean(vm, 1);
	return 1;
}

static int bangebehavior_SetFunction(lua_State *vm){
	//scene, function, set
	if(!lua_isfunction(vm, 2)){
		cout << "bange: SetFunction: First argument isn't a function" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	if(!lua_isboolean(vm, 3)){
		cout << "bange: SetFunction: Second argument must be a boolean" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	bange::base *base = bange::base::GetBaseFromProxy(1, vm);
	bange::behavior *behavior = dynamic_cast<bange::behavior *>(base);
	lua_pushboolean(vm, static_cast<int>(behavior->SetFunction(vm)));
	return 1;
}

static int bangebehavior_QuitFunction(lua_State *vm){
	//scene, function
	if(!lua_isfunction(vm, 2)){
		cout << "bange: QuitFunction: First argument isn't a function" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	bange::base *base = bange::base::GetBaseFromProxy(1, vm);
	bange::behavior *behavior = dynamic_cast<bange::behavior *>(base);
	lua_pushboolean(vm, static_cast<int>(behavior->QuitFunction(vm)));
	return 1;
}

static int bangebehavior_AddScheduleFunction(lua_State *vm){
	//scene, function, time
	if(!lua_isfunction(vm, 2)){
		cout << "bange: AddScheduleFunction: First argument isn't a function" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	if(!lua_isnumber(vm, 3)){
		cout << "bange: AddScheduleFunction: Second argument isn't a number" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	float time = static_cast<float>(lua_tonumber(vm, 3));
	if (time < 0){
		cout << "bange: AddScheduleFunction: Time must be greater or equal than zero" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	bange::base *base = bange::base::GetBaseFromProxy(1, vm);
	bange::behavior *behavior = dynamic_cast<bange::behavior *>(base);
	behavior->AddScheduleFunction(time, vm);
	lua_pushboolean(vm, 1);
	return 1;
}

static int bangebehavior_SetScheduleFunction(lua_State *vm){
	//scene, function, set
	if(!lua_isfunction(vm, 2)){
		cout << "bange: SetScheduleFunction: First argument isn't a function" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	if(!lua_isboolean(vm, 3)){
		cout << "bange: SetScheduleFunction: Second argument must be a boolean" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	bange::base *base = bange::base::GetBaseFromProxy(1, vm);
	bange::behavior *behavior = dynamic_cast<bange::behavior *>(base);
	lua_pushboolean(vm, static_cast<int>(behavior->SetScheduleFunction(vm)));
	return 1;
}

static int bangebehavior_ResetScheduleFunction(lua_State *vm){
	//userdata, function
	if (!lua_isfunction(vm, 2)){
		cout << "bange: ResetScheduleFunction: First argument ins't a function" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	bange::base *base = bange::base::GetBaseFromProxy(1, vm);
	bange::behavior *behavior = dynamic_cast<bange::behavior *>(base);
	behavior->ResetScheduleFunction(vm);
	lua_pushboolean(vm, 1);
	return 1;
}

static int bangebehavior_QuitScheduleFunction(lua_State *vm){
	//scene, function
	if(!lua_isfunction(vm, 2)){
		cout << "bange: QuitScheduleFunction: First argument isn't a function" << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	bange::base *base = bange::base::GetBaseFromProxy(1, vm);
	bange::behavior *behavior = dynamic_cast<bange::behavior *>(base);
	lua_pushboolean(vm, static_cast<int>(behavior->QuitScheduleFunction(vm)));
	return 1;
}
